/*********************************************************************************
 * Copyright: (C) 2018 LingYun IoT Studio
 * All rights reserved.
 *
 * Filename: socket_client_multi.c
 * Description: This file is a socket client sample source code that creates multiple
 * clients to connect to the socket server and send to/receive from it continuously.
 * 
 * Version: 1.0.0(10/23/2018)
 * Author: Guo Wenxue <guowenxue@gmail.com>
 * ChangeLog: 1, Release initial version on "2018-10-23 01:38:08 PM"
 * 
 ********************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include <getopt.h>

#include "socket_cnn.h"
#include "log_system.h"

// 服务器IP地址和端口
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8889
pthread_mutex_t db_mutex;

// 要发送的消息
#define MSG_STR "Hello, Unix Network Program World!"

// 默认客户端数量和连接间隔（秒）
#define DEFAULT_NUM_CLIENTS 10
#define DEFAULT_DELAY_SEC 1

struct option  opts[]=
{
    {"ipaddr", required_argument, NULL, 'i'},
    {"port", required_argument, NULL, 'p'},
    {"debug", no_argument, NULL, 'd'},
    {"help", no_argument, NULL, 'h'},
    {NULL, 0, NULL, 0}
};

void print_usage(char *progname)
{
	printf("%s usage:\n",progname);
    printf("-i(--ipaddr):sepcify server ipaddr.\n");
    printf("-p(--port):sepcify server listen port.\n");
    printf("-n(num_threads)):client_thread num.\n");
    printf("-d(--debug):  specify program running in debug mode\n");
    printf("-h(--help):   print this help information\n");

    printf("\nExample: %s -i 127.0.0.1 -p 8899 -t 5 -n 5 -d\n", progname);

	return ;
}

// 每个客户端的信息结构体
typedef struct {
    int 	client_id;   // 客户端编号
    int 	delay_sec;    // 每次连接之间的延迟时间（秒）
	char	*ipaddr;
	int		port;

} client_info_t;

// 每个客户端线程的执行函数
void *client_thread_func(void *args)
{
    client_info_t 	*info = (client_info_t *)args;

	char            *ipaddr = info->ipaddr;
    int             port = info->port;

    int 			conn_fd = -1;
    int 			rv = -1;
    char 			buf[1024];

    while(1)
    {
        // 创建套接字
        conn_fd = client_connect_to_server(ipaddr,port);

        if(conn_fd < 0)
        {
            printf("Client %d: 创建套接字失败: %s\n", info->client_id, strerror(errno));
            sleep(info->delay_sec); // 延时后重试
            continue;
        }

        // 发送消息到服务器
        if( write_data(conn_fd, MSG_STR, strlen(MSG_STR)) < 0 )
        {
            log_write(LOG_ERROR,__FILE__,__func__,__LINE__,"Client %d: 向服务器 [%s:%d] 发送数据失败: %s",info->client_id, ipaddr, port, strerror(errno));
            close(conn_fd);
            sleep(info->delay_sec); // 延时后重试
            continue;
        }

		log_write(LOG_INFO,__func__,__func__,__LINE__,"Client %d: 向服务器 [%s,%d] 发送%d 字节数据: '%s'", info->client_id, ipaddr,port,strlen(MSG_STR),MSG_STR);

        // 从服务器读取响应
        memset(buf, 0, sizeof(buf));
        rv = read(conn_fd, buf, sizeof(buf));
        if(rv < 0)
        {
            log_write(LOG_ERROR,__FILE__,__FILE__,__LINE__,"Client %d: 从服务器读取数据失败: %s", info->client_id, strerror(errno));
            close(conn_fd);
            sleep(info->delay_sec); // 延时后重试
            continue;
        }
        else if(rv == 0)
        {
            log_write(LOG_WARN,__FILE__,__func__,__LINE__,"Client %d: 服务器关闭了连接", info->client_id);
            close(conn_fd);
            sleep(info->delay_sec); // 延时后重试
            continue;
        }

        log_write(LOG_INFO,__func__,__func__,__LINE__,"Client %d: 从服务器读取到 %d 字节数据: '%s'", info->client_id, rv, buf);

        // 关闭连接
        close(conn_fd);

        // 等待一段时间后再次连接
        sleep(info->delay_sec);
    }

    return NULL;
}

int main(int argc, char **argv)
{
	int 			delay_sec = DEFAULT_DELAY_SEC;      // 连接间隔时间
    int 			i;
    
	pthread_t 		*threads;
    client_info_t 	*client_info;

	char          	*ipaddr = NULL;
	int             port = 0;
	int  			num_clients = 0;

	int  			ch = 0;
	char            *progname = argv[0];

	// 初始化日志系统，日志级别为DEBUG，输出到控制台和文件
    log_init("client_program.log", LOG_DEBUG, 1);

	// 初始化互斥锁
    if (pthread_mutex_init(&db_mutex, NULL) != 0)
    {
        log_write(LOG_ERROR, __FILE__, __func__, __LINE__, "初始化互斥锁失败");
        return EXIT_FAILURE;
    }

	// 解析命令行参数
	while((ch = getopt_long(argc,argv,"i:p:n:db",opts,NULL)) != -1 )
	{
		switch(ch)
		{
			case 'i':
				ipaddr = optarg;
				break;
            case 'p':
				port = atoi(optarg);
				break;
            case 'n':
				num_clients = atoi(optarg);
				break;
			case 'h':
				print_usage(progname);
				log_write(LOG_INFO, __FILE__, __func__, __LINE__, "显示帮助信息");
				return EXIT_SUCCESS;
            default:
				print_usage(progname);
                log_write(LOG_ERROR, __FILE__, __func__, __LINE__, "命令行参数错误");
				return EXIT_FAILURE;
		}
	}

	//检查命令行是否输入正确
	if( !(ipaddr && port && num_clients))
	{
		print_usage(progname);
		log_write(LOG_ERROR, __FILE__, __func__, __LINE__, "缺少必要参数");
		return EXIT_FAILURE;
	}

    log_write(LOG_INFO,__FILE__,__func__,__LINE__,"启动 %d 个客户端线程，每次连接间隔 %d 秒。", num_clients, delay_sec);

    // 为线程和客户端信息分配内存
    threads = malloc(sizeof(pthread_t) * num_clients);
    if(!threads)
    {
        log_write(LOG_ERROR,__FILE__,__func__,__LINE__,"分配线程内存失败");
        return -1;
    }

    client_info = malloc(sizeof(client_info_t) * num_clients);
    if(!client_info)
    {
        log_write(LOG_ERROR,__FILE__,__func__,__LINE__,"分配客户端信息内存失败");
        free(threads);
        return -1;
    }

    // 创建客户端线程
    for(i = 0; i < num_clients; i++)
    {
        client_info[i].client_id = i + 1;
        client_info[i].delay_sec = delay_sec;

		client_info[i].ipaddr = ipaddr;  
		client_info[i].port = port;

        if(pthread_create(&threads[i], NULL, client_thread_func, &client_info[i]) != 0)
        {
            log_write(LOG_ERROR,__FILE__,__func__,__LINE__,"创建客户端线程 %d 失败: %s\n", i+1, strerror(errno));
        }
    }

    //等待客户端线程结束,线程是无限循环的，因此主线程将一直等待）
    for(i = 0; i < num_clients; i++)
    {
        pthread_join(threads[i], NULL);
    }

	// 销毁互斥锁
    pthread_mutex_destroy(&db_mutex);
    //释放分配的内存
    free(threads);
    free(client_info);

    return 0;
}

